Skip to content

Validate define() and const values against explicit types in dynamicConstantNames#5648

Merged
staabm merged 12 commits into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-z2pzrmn
May 16, 2026
Merged

Validate define() and const values against explicit types in dynamicConstantNames#5648
staabm merged 12 commits into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-z2pzrmn

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When dynamicConstantNames is configured with an explicit type (e.g., BAR_CONSTANT: 'int|string|null'), PHPStan now validates that the value assigned via define(), const, or class constant declarations is compatible with that configured type. Previously, incompatible values were silently accepted.

Changes

  • Added getExplicitGlobalConstantType() and getExplicitClassConstantType() methods to src/Analyser/ConstantResolver.php to retrieve the configured explicit type from dynamicConstantNames without applying it as a resolution
  • Created src/Rules/Constants/ValueAssignedToDefineRule.php (level 2) — validates define('X', value) calls against explicit types in dynamicConstantNames
  • Created src/Rules/Constants/ValueAssignedToGlobalConstantRule.php (level 2) — validates file-level const X = value statements against explicit types in dynamicConstantNames
  • Extended src/Rules/Constants/ValueAssignedToClassConstantRule.php — when no PHPDoc @var or native type is present, now also checks against explicit types in dynamicConstantNames for class constants

Analogous cases probed

  • define() function calls — new ValueAssignedToDefineRule
  • File-level const statements — new ValueAssignedToGlobalConstantRule
  • Class constants — extended ValueAssignedToClassConstantRule to also validate against dynamicConstantNames explicit types when no other type source exists

Root cause

The dynamicConstantNames configuration with explicit types (associative entries like BAR_CONSTANT: 'int|string|null') was only used to resolve the constant's type when reading it, but never to validate the value when defining it. There were no rules checking that the value assigned in define(), const, or class constant declarations was compatible with the configured type.

Test

  • tests/PHPStan/Rules/Constants/ValueAssignedToDefineRuleTest.php — tests define() with compatible and incompatible values
  • tests/PHPStan/Rules/Constants/ValueAssignedToGlobalConstantRuleTest.php — tests file-level const with incompatible value
  • tests/PHPStan/Rules/Constants/ValueAssignedToClassConstantWithDynamicNamesRuleTest.php — tests class constants with incompatible value against dynamicConstantNames explicit type

Fixes phpstan/phpstan#14600

Comment thread src/Rules/Constants/ValueAssignedToGlobalConstantRule.php
Comment thread src/Analyser/ConstantResolver.php Outdated
Comment thread src/Rules/Constants/ValueAssignedToClassConstantRule.php
Comment thread src/Rules/Constants/ValueAssignedToDefineRule.php Outdated
@staabm staabm requested a review from VincentLanglet May 12, 2026 13:46
@VincentLanglet
Copy link
Copy Markdown
Contributor

I'll comment this without triggering a reaction from the bot to let you the choice @staabm because I'm unsure about this

  1. In ValueAssignedToClassConstantRule, can't we just do
$phpDocType = $constantReflection->getPhpDocType();
if ($phpDocType === null && $this->checkDynamicConstantNameValues) {
    $phpDocType = $this->constantResolver->getExplicitClassConstantType($classReflection->getName(), $constantName);
}

and rely on the existing logic ?

  1. Should we add a tip to explains where come from the type we use ?

  2. Should we use different identifier for ValueAssignedToDefineRule and ValueAssignedToGlobalConstantRule ?

@staabm
Copy link
Copy Markdown
Contributor

staabm commented May 12, 2026

  1. in ValueAssignedToClassConstantRule, can't we just do

if we do this, we get a error message which is misleading (hinting a phpdoc type)

PHPDoc tag @var for constant ValueAssignedToClassConstantDynamicNames\Foo::BAR with type int|string|null is incompatible with value false.

2. Should we add a tip to explains where come from the type we use ?

I tried to improve the error message

3. Should we use different identifier for ValueAssignedToDefineRule and ValueAssignedToGlobalConstantRule ?

done

staabm and others added 7 commits May 16, 2026 08:47
…amicConstantNames`

- Add `getExplicitGlobalConstantType()` and `getExplicitClassConstantType()` methods to `ConstantResolver` to retrieve the configured type from `dynamicConstantNames` without applying it
- Add `ValueAssignedToDefineRule` (level 2) that validates `define()` call values against the explicit type configured in `dynamicConstantNames`
- Add `ValueAssignedToGlobalConstantRule` (level 2) that validates file-level `const` statement values against the explicit type configured in `dynamicConstantNames`
- Extend `ValueAssignedToClassConstantRule` to also validate class constant values against explicit types in `dynamicConstantNames` when no PHPDoc or native type is present
The new rules (ValueAssignedToDefineRule, ValueAssignedToGlobalConstantRule)
and the dynamicConstantNames check in ValueAssignedToClassConstantRule are
now only enabled when bleeding edge is active, via the new
checkDynamicConstantNameValues feature toggle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…plicit*Type methods

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactor resolveConstantType/resolveClassConstantType to reuse
getExplicitGlobalConstantType/getExplicitClassConstantType instead of
duplicating the type string resolution logic.

Update ValueAssignedToDefineRule to iterate over all constant name
strings instead of bailing out when count !== 1, so define() calls
with union-typed names are checked too.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nedToDefineRule

- Refactor resolveConstantType() and resolveClassConstantType() to reuse
  getExplicitGlobalConstantType() and getExplicitClassConstantType()
  instead of duplicating the TypeStringResolver lookup logic.
- Update ValueAssignedToDefineRule to iterate over all constant name
  strings from the define() call, not just when there is exactly one.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@staabm staabm force-pushed the create-pull-request/patch-z2pzrmn branch from 4268892 to 5b39181 Compare May 16, 2026 06:47
Comment thread src/Analyser/ConstantResolver.php Outdated
Comment thread src/Analyser/ConstantResolver.php Outdated
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread src/Rules/Constants/ValueAssignedToClassConstantRule.php
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
$verbosity = VerbosityLevel::getRecommendedLevelByType($configuredType, $valueExprType);
return [
RuleErrorBuilder::message(sprintf(
'Configuration defined type for constant %s::%s (%s) is incompatible with value %s.',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'Configuration defined type for constant %s::%s (%s) is incompatible with value %s.',
'Configuration defined type for constant %s::%s (%s) does not accept value %s.',

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Changed the error message wording from "is incompatible with value" to "does not accept value" in all three rule files (ValueAssignedToClassConstantRule, ValueAssignedToDefineRule, ValueAssignedToGlobalConstantRule) and their corresponding tests. All tests pass and make phpstan reports no errors.

$verbosity = VerbosityLevel::getRecommendedLevelByType($configuredType, $valueType);

$errors[] = RuleErrorBuilder::message(sprintf(
'Configuration defined type for constant %s (%s) is incompatible with value %s.',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'Configuration defined type for constant %s (%s) is incompatible with value %s.',
'Configuration defined type for constant %s (%s) does not accept value %s.',

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread src/Analyser/ConstantResolver.php
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread src/Rules/Constants/ValueAssignedToGlobalConstantRule.php
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@staabm staabm merged commit 9abd515 into phpstan:2.1.x May 16, 2026
658 of 660 checks passed
@staabm staabm deleted the create-pull-request/patch-z2pzrmn branch May 16, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants